/*	$Id: conf.y 325 2007-05-16 21:49:05Z phrakt $	*/
/*
 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

%{
#include <sys/param.h>

#include <errno.h>
#include <ctype.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>

#include <canopy/queue.h>
#include <canopy/string.h>

#include "log.h"
#include "conf.h"


/* configuration variable */
struct conf_var {
	char *cv_name;
	int   cv_type;

	union {
		uint64_t         num;
		char            *str;
		uint8_t         *bstr;
	} cv_val;
	size_t  cv_len;

	TAILQ_ENTRY(conf_var) cv_list;
};


/* configuration directive information */
struct conf_dir {
	char  *cd_name;
	int    cd_minargc;
	int    cd_maxargc;
	int  (*cd_hdlr)(int, char **);

	TAILQ_ENTRY(conf_dir) cd_list;
};



/* reserved characters */
static const char conf_reserved[] = "=${}[]";


/* parser states */
#define CFP_STATE_INIT     0
#define CFP_STATE_PARSING  1
#define CFP_STATE_DONE     2
#define CFP_STATE_ERROR    3
#define CFP_STATE_FUBAR    10



#define CFP_BGETC(p, c)					\
	do {						\
		if ((p)->cf_bused == 0) 		\
			break;				\
		c = *((p)->cf_bdata++);			\
		(p)->cf_bused--;			\
	} while (0)


#define CFP_BUNGETC(p, c)				\
	do {						\
		if ((p)->cf_bdata == (p)->cf_buf) 	\
			break;				\
		*(--(p)->cf_bdata) = (char)(c);		\
		(p)->cf_bused++;			\
	} while (0)


#define CFP_SETERR(p, e)				\
	do {						\
		(p)->cf_state = CFP_STATE_ERROR;	\
		(p)->cf_error = (e);			\
	} while (0)

struct cnp_conf {
	int     cf_flags;
	int     cf_state;
	int     cf_error;			/* error code, if any */

	char   *cf_file;			/* file being parsed, if any */
	FILE   *cf_fin;
	int     cf_lineno;			/* current line number */

	char   *cf_buf;
	char   *cf_bdata;			/* start of data in buffer */
	size_t  cf_blen;
	size_t  cf_bused;			/* bytes used in buffer */

	TAILQ_HEAD(, conf_var) cf_vars;		/* variables */
	TAILQ_HEAD(, conf_dir) cf_dirs;		/* directives */

	/* argument vector for directives */
	char   *cf_argv[CONF_DIR_MAXARG];
	int     cf_argc;
};


struct yystype {
	union {
		uint64_t          num;
		char             *str;
		uint8_t          *bstr;
		struct conf_dir  *dir;
	} v;
	size_t  vlen;
	u_int   type;

	int lineno;
};


#define YYSTYPE struct yystype


static int  conf_getc   (CONF *);
static int  conf_ungetc (CONF *, int);
static void conf_log  (CONF *, const char *, ...);
static int  yyerror     (const char *, ...);
static int  yylex       (void);
#if 0
static int  yylex_qstr  (int);
#endif
static int  yyparse     (void);

static struct conf_var* conf_newvar     (const char *, int);
static struct conf_var* conf_findvar    (CONF *, const char *);
static int              conf_printvar   (const struct conf_var *, char *, size_t);
static int              conf_chkvarname (const char *);

static struct conf_dir* conf_finddir    (CONF *, const char *);
static int              conf_rundir     (CONF *);

/*
 * In order to have a reentrant parser, we have to use a big lock model
 */
static CONF *curpsr;	/* pointer to current parser */

#ifdef THREAD_SAFE
static pthread_mutex_t cfp_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif

%}

%token  <v.str>   STRING
%token  <v.num>   NUMBER
%token  <v.dir>   DIRECTIVE
%%

conf		: /* empty */
		| conf '\n'
		| conf var_assign '\n'
		| conf DIRECTIVE
		{
			curpsr->cf_argv[0] = $2->cd_name;
			curpsr->cf_argc = 1;
		} dir_argv '\n'
		{
			if (conf_rundir(curpsr) == -1) {
				YYERROR;
			}
		}
		| conf error '\n'       { CFP_SETERR(curpsr, CONF_ERR_PARSE); }
		;

var_assign	: STRING '=' STRING
		{
			conf_setvar(curpsr, $1, CONF_VAR_STR,
			   $3, strlen($3));
		}
		;


dir_argv	: /* empty */
		| dir_argv STRING
		{
			if (curpsr->cf_argc == CONF_DIR_MAXARG) {
				YYERROR;
			}

			curpsr->cf_argv[curpsr->cf_argc++] = $2;
		}

%%


/*
 * conf_getc()
 *
 * Retrieve the next available character from the stream being parsed by
 * <cp>.
 */
static int
conf_getc(CONF *cp)
{
	int c;

	/* check if we've got something in the parse buffer first */
	if (cp->cf_bused > 0) {
		CFP_BGETC(cp, c);
		return (c);
	}

	c = getc(cp->cf_fin);

	return (c);
}


/*
 * conf_ungetc()
 *
 * Push back the character <c> in the parser <cp>'s parse buffer.  We do not
 * rely on ungetc() because it guarantees only one character pushback and,
 * although this mostly what we need, some cases may arise where we need
 * more pushback guaranteed.
 */
static int
conf_ungetc(CONF *cp, int c)
{
	CFP_BUNGETC(cp, c);

	return (0);
}


/*
 * conf_alloc()
 *
 * Allocate a new configuration parser and initialize its internals for the
 * parsing of files which have the format specified in <style>.
 * Returns a pointer to the new parser on success, or NULL on failure.
 */
CONF*
conf_alloc(int flags)
{
	CONF *cp;

	if ((cp = (CONF *)malloc(sizeof(*cp))) == NULL) {
		return (NULL);
	}
	memset(cp, 0, sizeof(*cp));

	cp->cf_flags = flags;
	cp->cf_lineno = 1;

	cp->cf_file = NULL;
	cp->cf_fin = NULL;

	TAILQ_INIT(&(cp->cf_vars));
	TAILQ_INIT(&(cp->cf_dirs));

	cp->cf_blen = CONF_BUF_DEFSIZE;
	if ((cp->cf_buf = (char *)malloc(cp->cf_blen)) == NULL) {
		conf_free(cp);
		return (NULL);
	}

	cp->cf_bdata = cp->cf_buf + (cp->cf_blen / 2);
	cp->cf_bused = 0;

	cp->cf_argc = 0;
	memset(cp->cf_argv, 0, sizeof(cp->cf_argv));

	return (cp);
}


/*
 * conf_free()
 *
 * Free all resources currently used by the parser <cp>.  Any further
 * operation on this parser will have undefined behaviour.
 */
void
conf_free(CONF *cp)
{
	if (cp != NULL) {
		if (cp->cf_file != NULL)
			free(cp->cf_file);

		if (cp->cf_fin != NULL)
			(void)fclose(cp->cf_fin);

		if (cp->cf_buf != NULL)
			free(cp->cf_buf);
		free(cp);
	}
}

/*
 * conf_reset()
 *
 * Reset the parser's internal states.  Once this operation has been performed,
 * the parser should be ready to parser a new configuration file.
 */
void
conf_reset(CONF *cp)
{
	cp->cf_state = CFP_STATE_INIT;
	cp->cf_error = CONF_ERR_NOERR;
	cp->cf_lineno = 0;

	if (cp->cf_file != NULL) {
		free(cp->cf_file);
		cp->cf_file = NULL;
	}
}

/*
 * conf_getfl()
 *
 * Returns the configuration parser's flags.
 */
int
conf_getfl(CONF *cp)
{
	return (cp->cf_flags);
}

/*
 * conf_setfl()
 *
 * Set the parser's flags to <flags>.
 */
int
conf_setfl(CONF *cp, int flags)
{
	cp->cf_flags = flags;
	return (0);
}

/*
 * conf_parse()
 *
 * Parse the contents of the configuration file <conf>.
 * Returns 0 on success, or -1 on error.
 */
int
conf_parse(CONF *cp, const char *conf)
{
	int ret = 0;

	if ((cp->cf_file = strdup(conf)) == NULL) {
		cnp_log(CNP_LOG_ERR, "failed to copy path");
		CFP_SETERR(cp, CONF_ERR_NOMEM);
		return (-1);
	}

	if ((cp->cf_fin = fopen(conf, "r")) == NULL) {
		cnp_log(CNP_LOG_ERRNO, "failed to open %s", conf);
		return (-1);
	}

#ifdef THREAD_SAFE
	if (pthread_mutex_lock(&cfp_mutex) != 0)
		return (-1);
#endif

	/* set ourself as the current parser */
	curpsr = cp;
	if (yyparse() != 0)
		ret = -1;

#ifdef THREAD_SAFE
	(void)pthread_mutex_unlock(&cfp_mutex);
#endif

	(void)fclose(cp->cf_fin);
	cp->cf_fin = NULL;

	return (ret);
}

/*
 * conf_getvar()
 *
 * Get a macro <macro>'s associated value.  Returns the value string on
 * success, or NULL if no such macro exists.
 */
int
conf_getvar(CONF *cp, const char *name, void *dst, size_t len)
{
	struct conf_var *cvp;

	TAILQ_FOREACH(cvp, &(cp->cf_vars), cv_list)
		if (strcmp(cvp->cv_name, name) == 0)
			return (cvp->cv_type);

	return (-1);
}

/*
 * conf_setvar()
 *
 * Set the value of the variable whose name is <name> to the contents of
 * <val>, which points to an object appropriate for the type <type>.
 * The following types are expected depending on the value of <type>:
 *
 *	CONF_VAR_NONE		=> ignored
 *	CONF_VAR_BOOL		=> (u_int *)        (non-zero reduced to 1)
 *	CONF_VAR_INT		=> (int64_t *) 
 *	CONF_VAR_UINT		=> (int64_t *) 
 *	CONF_VAR_STR		=> (char *)
 *	CONF_VAR_BINSTR		=> (uint8_t *)
 *	CONF_VAR_STRUCT		=> depends on definition
 *	CONF_VAR_IPV4ADDR	=> (struct in_addr *)
 *	CONF_VAR_IPV6ADDR	=> (struct in6_addr *)
 *	CONF_VAR_PATH		=> (char *)
 */
int
conf_setvar(CONF *cp, const char *name, int type, const void *val,
    size_t sz)
{
	int ret;
	struct conf_var *cvp;

	/* validate arguments */
	if ((name == NULL) || (type < CONF_VAR_NONE) ||
	    (type > CONF_VAR_MAXTYPE)) {
		CFP_SETERR(cp, CONF_ERR_INVARG);
		return (-1);
	}

	if (!conf_chkvarname(name)) {
		return (-1);
	}

	if ((cvp = conf_findvar(cp, name)) == NULL) {
		if (cp->cf_flags & CONF_STRICTVAR) {
			conf_log(cp, "assignment to undeclared variable `%s' "
			    "while in strict variable mode", name);
			/* no setting without prior declaration */
			CFP_SETERR(cp, CONF_ERR_STRICT);
			return (-1);
		}

		if ((cvp = conf_newvar(name, type)) == NULL) {
			CFP_SETERR(cp, CONF_ERR_NOMEM);
			return (-1);
		}

		TAILQ_INSERT_TAIL(&(cp->cf_vars), cvp, cv_list);
	} else if (cvp->cv_type != type) {
		if (cp->cf_flags & CONF_STRICTTYPE) {
			conf_log(cp, "assignment to variable `%s' does not follow type restrictions");
			CFP_SETERR(cp, CONF_ERR_STRICT);
			return (-1);
		}

		if (cvp->cv_type == CONF_VAR_STR)
			free(cvp->cv_val.str);
		else if (cvp->cv_type == CONF_VAR_BINSTR)
			free(cvp->cv_val.bstr);
	}

	ret = 0;

	switch (type) {
	case CONF_VAR_BOOL:
	case CONF_VAR_INT:
	case CONF_VAR_UINT:
		switch (sz) {
		case sizeof(uint8_t):
			cvp->cv_val.num = *(const uint8_t *)val;
			break;
		case sizeof(uint16_t):
			cvp->cv_val.num = *(const uint16_t *)val;
			break;
		case sizeof(uint32_t):
			cvp->cv_val.num = *(const uint16_t *)val;
			break;
		case sizeof(uint64_t):
			cvp->cv_val.num = *(const uint64_t *)val;
			break;
		default:
			CFP_SETERR(cp, CONF_ERR_INVARG);
			ret = -1;
			break;
		}

		if ((type == CONF_VAR_BOOL) && (cvp->cv_val.num != 0))
			cvp->cv_val.num = 1;
		break;
	case CONF_VAR_STR:
		if ((cvp->cv_val.str = strdup((const char *)val)) == NULL) {
			CFP_SETERR(cp, CONF_ERR_NOMEM);
			ret = -1;
		} else
			cvp->cv_len = strlen(cvp->cv_val.str);
		break;
	case CONF_VAR_BINSTR:
		if ((cvp->cv_val.bstr = (uint8_t *)malloc(sz)) == NULL) {
			CFP_SETERR(cp, CONF_ERR_NOMEM);
			ret = -1;
		} else {
			memcpy(cvp->cv_val.bstr, val, sz);
			cvp->cv_len = sz;
		}
		break;
	default:
		CFP_SETERR(cp, CONF_ERR_INVARG);
		ret = -1;
		break;
	}

	return (ret);
}


/*
 * conf_newvar()
 *
 * Allocate a new data structure to store a config variable with name <name>,
 * and of type <type> (no checking is performed to validate the type passed
 * as argument).
 */
static struct conf_var*
conf_newvar(const char *name, int type)
{
	struct conf_var *cvp;

	if ((cvp = (struct conf_var *)malloc(sizeof(*cvp))) == NULL)
		return (NULL);

	if ((cvp->cv_name = strdup(name)) == NULL) {
		free(cvp);
		return (NULL);
	}
	cvp->cv_type = type;

	return (cvp);
}

/*
 * conf_findvar()
 *
 * Look for the variable whose name is <name> in the context of the parser
 * <cp> and return a pointer to its structure if found, or NULL otherwise.
 */
static struct conf_var *
conf_findvar(CONF *cp, const char *name)
{
	struct conf_var *cvp;

	TAILQ_FOREACH(cvp, &(cp->cf_vars), cv_list)
		if (strcmp(cvp->cv_name, name) == 0)
			return (cvp);

	return (NULL);
}


/*
 * conf_printvar()
 *
 * Generate a human-readable string representing the value of the
 * configuration variable <cvp> and store the resulting string in <buf>,
 * which is of size <len>.  The result is guaranteed to be NUL-terminated
 * on success.
 * Returns the number of bytes stored in <buf> on success, or -1 on failure.
 */
int
conf_printvar(const struct conf_var *cvp, char *buf, size_t len)
{
	int ret = 0;

	switch (cvp->cv_type) {
	case CONF_VAR_NONE:
		buf[0] = '\0';
		break;
	case CONF_VAR_BOOL:
		if (cvp->cv_val.num == 0)
			buf[0] = '0';
		else
			buf[0] = '1';
		buf[1] = '\0';
		ret = 1;
		break;
	case CONF_VAR_INT:
		ret = snprintf(buf, len, "%lld", cvp->cv_val.num);
		break;
	case CONF_VAR_UINT:
		ret = snprintf(buf, len, "%llu", cvp->cv_val.num);
		break;
	case CONF_VAR_STR:
		ret = (int)strlcpy(buf, cvp->cv_val.str, len);
		break;
	case CONF_VAR_BINSTR:
		break;
	case CONF_VAR_STRUCT:
		break;
	default:
		ret = -1;
	}

	return (ret);
}

/*
 * conf_chkvarname()
 *
 * Validate the proposed variable name <name>.
 * Returns 1 if the name is OK, 0 if it doesn't conform to the variable
 * naming convention.
 */
static int
conf_chkvarname(const char *name)
{
	const char *np;

	if (!isalpha(*name) && (*name != '_'))
		return (0);

	for (np = name + 1; *np != '\0'; np++)
		if (!isalnum(*name) && (*name != '_'))
			return (0);

	return (1);
}

/*
 * conf_setdir()
 *
 */
int
conf_setdir(CONF *cp, const char *name, int (*hdlr)(int, char **), int min,
    int max)
{
	struct conf_dir *cdp;

	if ((cdp = (struct conf_dir *)malloc(sizeof(*cdp))) == NULL) {
		return (-1);
	}

	if ((cdp->cd_name = strdup(name)) == NULL) {
		return (-1);
	}

	cdp->cd_hdlr = hdlr;
	cdp->cd_minargc = min;
	cdp->cd_maxargc = max;

	TAILQ_INSERT_TAIL(&(cp->cf_dirs), cdp, cd_list);

	return (0);
}

/*
 * conf_finddir()
 *
 * Look for the directive whose name is <name> in the context of the parser
 * <cp> and return a pointer to its structure if found, or NULL otherwise.
 */
static struct conf_dir *
conf_finddir(CONF *cp, const char *name)
{
	struct conf_dir *cdp;

	TAILQ_FOREACH(cdp, &(cp->cf_dirs), cd_list)
		if (strcmp(cdp->cd_name, name) == 0)
			return (cdp);

	return (NULL);
}

/*
 * conf_rundir()
 *
 * Run the directive specified in the first entry of the directive argument
 * vector, along with the other arguments supplied.  After the directive was
 * run, the argument vector is reinitialized to prepare for the next call.
 * Returns the directive's return value on success, or -1 on failure.
 */
static int
conf_rundir(CONF *cf)
{
	int ret;
	struct conf_dir *cd;

	cd = conf_finddir(cf, cf->cf_argv[0]);

	if ((cd->cd_minargc > 0) && (cf->cf_argc < cd->cd_minargc)) {
		conf_log(cf, "not enough arguments to `%s' directive",
		    cd->cd_name);
		return (-1);
	}
	else if ((cd->cd_maxargc >= 0) && (cf->cf_argc > cd->cd_maxargc)) {
		conf_log(cf, "too many arguments to `%s' directive",
		    cd->cd_name);
		return (-1);
	}

	ret = (*cd->cd_hdlr)(cf->cf_argc, cf->cf_argv);

	/* reset the argument vector */
	memset(cf->cf_argv, 0, sizeof(cf->cf_argv));
	cf->cf_argc = 0;

	return (ret);
}

/*
 * conf_log()
 *
 * Send a debug message out only if the CONF_DEBUG flag is set.  The output
 * is always terminated with a newline.
 */
void
conf_log(CONF *cp, const char *fmt, ...)
{
	char *nfmt;
	va_list vap;

	if (!(cp->cf_flags & CONF_DEBUG))
		return;

	asprintf(&nfmt, "%s[%u]: %s", cp->cf_file, cp->cf_lineno, fmt);

	va_start(vap, fmt);
	cnp_vlog(CNP_LOG_DEBUG, nfmt, vap);
	va_end(vap);
	free(nfmt);
}

/*
 * yylex()
 *
 * Our own lexer.
 */
static int
yylex(void)
{
	int ret, c, brkt;
	char buf[CONF_TOK_MAXLEN], *bp, *ep;
	struct conf_var *var;

	buf[0] = '\0';
	buf[1] = '\0';

lex_start:
	bp = buf;
	ep = buf + sizeof(buf) - 1;

	yylval.lineno = curpsr->cf_lineno;

	/* skip whitespace */
	while (((c = conf_getc(curpsr)) == ' ') || (c == '\t'))
		;

	if (c == '#') {
		/* strip comment up to end of line */
		do {
			c = conf_getc(curpsr);
		} while ((c != '\n') && (c != EOF));
	}

	if (c == EOF)
		c = 0;
	else if (c == '\n')
		yylval.lineno = curpsr->cf_lineno++;
	else if ((c == '=') || (c == '[') || (c == ']'))
		buf[0] = c;		/* nothing */
	else if (c == '$') {
		c = conf_getc(curpsr);
		brkt = (c == '{') ? 1 : 0;
		if ((brkt == 1) && ((c = conf_getc(curpsr)) == '}'))
			CFP_SETERR(curpsr, CONF_ERR_PARSE);

		do {
			*bp++ = (char)c;
			if (bp == ep) {
				CFP_SETERR(curpsr, CONF_ERR_BADNAME);
				return (-1);
			}
			c = conf_getc(curpsr);
		} while (isalnum(c) || c == '_');
		if (!brkt)
			conf_ungetc(curpsr, c);
		*bp = '\0';

		if ((var = conf_findvar(curpsr, buf)) == NULL) {
			if (curpsr->cf_flags & CONF_STRICTVAR) {
				CFP_SETERR(curpsr, CONF_ERR_STRICT);
				return (-1);
			}
		}
		else if ((ret = conf_printvar(var, curpsr->cf_buf,
		    curpsr->cf_blen)) < 0)
			return (-1);
		else {
			curpsr->cf_bdata = curpsr->cf_buf;
			curpsr->cf_bused = (size_t)ret;
		}

		goto lex_start;
	} else if ((c == '"') || (c == '\'')) {
	} else {
		do {
			*bp++ = (char)c;
			if (bp == ep) {
				CFP_SETERR(curpsr, CONF_ERR_TOKLEN);
				return (-1);
			}

			c = conf_getc(curpsr);
		} while ((c != EOF) && !isblank(c) && (c != '\n'));
		conf_ungetc(curpsr, c);
		*bp = '\0';

		if ((yylval.v.dir = conf_finddir(curpsr, buf)) != NULL)
			c = DIRECTIVE;
		else {
			c = STRING;
			if ((yylval.v.str = strdup(buf)) == NULL)
				return (-1);
		}
	}

#if 0
	printf("token[%d] = `%s'\n", c, buf);
#endif

	return (c);
}

#if 0
/*
 * yylex_qstr()
 *
 * Take care of quoted strings.  The <quote> argument tells us what kind of
 * quote opened the string.
 */
static int
yylex_qstr(int quote)
{
	int c;
	char buf[10];
	char *bp, *ep;

	while ((c = conf_getc(curpsr)) != quote) {
		if (quote == '"') {
			if (c == '\\') {
				c = conf_getc(curpsr);
				switch (c) {
				case 'n':
					c = '\n';
					break;
				case 'r':
					c = '\r';
					break;
				case 't':
					c = '\t';
					break;
				default:
					break;
				}
			}
		}

		*bp++ = (char)c;
		if (bp == ep) {
			CFP_SETERR(curpsr, CONF_ERR_TOKLEN);
			return (-1);
		}
	}
	*bp = '\0';
	if ((yylval.v.str = strdup(buf)) == NULL) {
		CFP_SETERR(curpsr, CONF_ERR_NOMEM);
		return (-1);
	}
	c = STRING;

	return (c);
}
#endif

/*
 * yyerror()
 *
 * Our own hacked up yyerror() that accepts a printf(3)-compatible format
 * string (and subsequent arguments) instead of a regular string.
 */
static int
yyerror(const char *fmt, ...)
{
	char *nfmt;
	va_list vap;

	va_start(vap, fmt);

	if (asprintf(&nfmt, "%s:%d: %s", curpsr->cf_file,
	    yylval.lineno, fmt) == -1) {
		return (-1);
	}
	cnp_vlog(CNP_LOG_ERR, nfmt, vap);

	free(nfmt);
	va_end(vap);
	return (0);

}
